const { SyncHook } = require("tapable")
const { toUnixPath, tryExtensions } = require("./utils")
const fs = require('fs')
const path = require("path")
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generator = require('@babel/generator').default;
const t = require('@babel/types');

class Compiler {
    constructor(options) {
        this.options = options

        //设置根路径
        this.rootPath = this.options.context || toUnixPath(process.cwd())

        this.hooks = {
            //编译开始钩子
            run: new SyncHook(),
            //输出到output之前执行
            emit: new SyncHook(),
            //全部完成编译执行
            done: new SyncHook()
        }

        //存放所有入口模块对象
        this.entries = new Set()
        //存放所有依赖模块对象
        this.modules = new Set()
        //存放所有的代码块对象
        this.chunks = new Set()
        //存放本次产出的文件对象
        this.assets = new Set()
        //存放本次编译的所有产出的文件名
        this.files = new Set()
    }



    //启动编译，接收一个callback
    run(callback) {
        //触发编译开始的钩子
        this.hooks.run.call()
        //获取入口配置对象
        const entry = this.getEntry()
        //编译入口文件
        this.buildEntryModule(entry)
    }

    //获取入口文件路径
    getEntry() {
        let entry = Object.create(null)
        const { entry: optionsEntry } = this.options
        if (typeof optionsEntry === 'string') {
            entry['main'] = optionsEntry
        } else {
            entry = optionsEntry
        }

        Object.keys(entry).forEach(key => {
            const value = entry[key]
            if (!path.isAbsolute(value)) {
                //转为绝对路径并统一分隔符为/
                entry[key] = toUnixPath(path.join(this.rootPath, value))
            }
        })
        return entry
    }

    buildEntryModule(entry) {
        Object.keys(entry).forEach(entryName => {
            const entryPath = entry[entryName]
            const entryObj = this.buildModule(entryName, entryPath)
            this.entries.add(entryObj)
        })
        console.log('ertries', this.entries)
    }

    //模块编译方法
    buildModule(moduleName, modulePath) {
        //读取文件原始代码
        const originSourceCode = (this.originSourceCode = fs.readFileSync(modulePath, 'utf-8'))
        console.log('读取文件原始代码', fs.readFileSync(modulePath, 'utf-8'))
        //这里是修改后的代码
        this.moduleCode = originSourceCode
        //调用loader处理代码
        this.handleLoader(modulePath)
        //调用webpack编译，获得最终的模块对象
        const module = this.handleWebpackCompiler(moduleName, modulePath)

        return module
    }

    //匹配loader处理
    handleLoader(modulePath) {
        const matchLoaders = []
        //获取所有传入的loader规则
        const rules = this.options.module.rules
        rules.forEach(loader => {
            const testRule = loader.test
            if (testRule.test(modulePath)) {
                if (loader.loader) {
                    matchLoaders.push(loader.loader)
                } else {
                    matchLoaders.push(...loader.use)
                }
            }
            //倒序执行loader传入源代码
            for (let i = matchLoaders.length - 1; i >= 0; i--) {
                //require引入对应loader
                const loaderFn = require(matchLoaders[i])
                //通过loader同步处理每一次编译的moduleCode
                this.moduleCode = loaderFn(this.moduleCode)
            }
        })
    }

    handleWebpackCompiler(moduleName, modulePath) {
        //将当前模块相对于当前根路径计算出模块id
        const moduleId = './' + path.posix.relative(this.rootPath, modulePath)
        //创建模块对象
        const module = {
            id: moduleId,
            dependencies: new Set(), //该模块所依赖模块的绝对路径地址
            name: [moduleName] //该模块所属入口文件
        }
        //调用babel分析我们的代码
        console.log('调用babel分析我们的代码', this.moduleCode)
        const ast = parser.parse(this.moduleCode, {
            sourceType: 'module'
        });
        //深度优先遍历语法tree
        traverse(ast, {
            CallExpression: (nodePath) => {
                const node = nodePath.node
                if (node.callee.name === 'require') {
                    //获得源代码中引入模块相对路径
                    const requirePath = node.arguments[0].value
                    //寻找模块绝对路径 当前模块路径+require()对应相对路径
                    const moduleDirName = path.posix.dirname(modulePath)
                    const absolutePath = tryExtensions(
                        path.posix.join(moduleDirName, requirePath),
                        this.options.resolve.extensions,
                        requirePath,
                        moduleDirName
                    )

                    //生成moduleId 针对于根路径的模块id 添加进入新的依赖模块路径
                    const moduleId = './' + path.posix.relative(this.rootPath, absolutePath)
                    //通过babel修改源代码中的require变成__webpack_require__语句
                    node.callee = t.identifier('__webpack_require__')
                    //修改源代码中require语句引入的模块 全部修改为相对于根路径来处理
                    node.arguments = [t.stringLiteral(moduleId)]
                    //为当前模块添加require语句造成的依赖（内容为相对于根路径的模块id）
                    module.dependencies.add(moduleId)
                }
            }
        })
        //遍历结束根据AST生成新的代码
        const { code } = generator(ast)
        //为当前模块挂载新生的代码
        module._source = code

        return module
    }
}



module.exports = Compiler